#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
THN TELEPORT version 2

Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

This library is dual-licensed.
  + For commercial software, please obtain a commercial license from THN Solutions LLC.

  + For free software, this library is licensed under GPL version 3. A summary of GPLv3 is
    listed below. You should also find a copy of GPLv3 as file License_GPLv3.txt included
	with the source-code files.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as published by
    the Free Software Foundation  of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

#region Using directives
using System;
using System.Collections;
using System.ComponentModel;

using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Serialization.Formatters;

using Thn;
using Thn.Application.Configuration;

using Thn.Communication.Teleporting;
using Thn.Communication.Teleporting.Settings;
#endregion

namespace Thn.Communication.Teleporting.Connectors.Remoting
{
    /// <summary>
    /// Server-side connector. Can act as a remoting server.
    /// </summary>
    public class ServerConnector : Component, IServerConnector, ITeleportConnector
    {
        #region Events

        #region Started
        /// <summary>
        /// This event is fired after the server has started
        /// </summary>
        public event EventHandler Started;
        void RaiseStarted()
        {
            EventHandler handler = Started;
            if (handler != null) handler(this, EventArgs.Empty);
        }

        #endregion

        #region Stopped
        /// <summary>
        /// This event is fired after the server has stopped
        /// </summary>
        public event EventHandler Stopped;
        void RaiseStopped()
        {
            EventHandler handler = Stopped;
            if (handler != null) handler(this, EventArgs.Empty);
        }

        #endregion

        #endregion

        #region Fields
        /// <summary>
        /// .NET Remoting channel
        /// At least one channel must be registered with the remoting framework before a remote object can be called
        /// </summary>
        IChannel mChannel = null;
        #endregion

        #region Properties

        #region Port
        private int mPort = 8080;
        /// <summary>
        /// Gets/Sets port value
        /// </summary>
        public int Port
        {
            get { return mPort; }
            set { mPort = value; }
        }
        #endregion

        #region Uri
        private string mUri = "TeleportService";
        /// <summary>
        /// Gets/Sets URI address
        /// </summary>
        public string Uri
        {
            get { return mUri; }
            set { mUri = value; }
        }
        #endregion

        #region Channel Type
        private ChannelType mChannelType = ChannelType.Tcp;
        /// <summary>
        /// Gets/Sets type of tramission protocol.
        /// <para>Default is <see cref="ChannelType.Tcp"/></para>
        /// </summary>
        public ChannelType ChannelType
        {
            get { return mChannelType; }
            set { mChannelType = value; }
        }
        #endregion

        #region Channel Name
        private string mChannelName = "TeleportChannel";
        /// <summary>
        /// Gets/Sets name of the IPC channel used in IPC remoting.
        /// </summary>
        public string ChannelName
        {
            get { return mChannelName; }
            set { mChannelName = value; }
        }
        #endregion

        #region Formatter
        private FormatterType mFormatter = FormatterType.Soap;
        /// <summary>
        /// Gets/Sets type of formatter
        /// </summary>
        public FormatterType Formatter
        {
            get { return mFormatter; }
            set { mFormatter = value; }
        }
        #endregion

        #region Is Started
        static private bool mIsStarted;
        /// <summary>
        /// Checks whether the server has been started
        /// </summary>
        public bool IsStarted
        {
            get { return mIsStarted; }
        }
        #endregion

        #region Auto Start
        private bool mAutoStart = true;
        /// <summary>
        /// Gets/Sets whether this connector shall start listening immediately after initialized.
        /// </summary>
        public bool AutoStart
        {
            get { return mAutoStart; }
            set { mAutoStart = value; }
        }
        #endregion

        #region Effective Uri
        private string mEffectiveUri;
        /// <summary>
        /// Gets the physical URI that corresponds to this server's channel.
        /// </summary>
        public string EffectiveUri
        {
            get { return mEffectiveUri; }
        }
        #endregion

        #endregion

        #region Register Channel
        /// <summary>
        /// Register Channel: tcp or http with port number
        /// </summary>
        void RegisterChannel()
        {
            IServerFormatterSinkProvider serverProvider = null;
            IClientFormatterSinkProvider clientProvider = null;

            #region Create Formatter Sink Provider
            //Use the default BINARY formatting
            if (mFormatter == FormatterType.Binary)
            {
                serverProvider = new BinaryServerFormatterSinkProvider();
                clientProvider = null;
            }
            //Use the default SOAP formatting
            else if (mFormatter == FormatterType.Soap)
            {
                serverProvider = new SoapServerFormatterSinkProvider();
                clientProvider = null;
            }
            #endregion

            #region Common initialization for both SOAP and binary
            IDictionary props = new Hashtable();

            #endregion

            #region Create channel
            if (mChannelType == ChannelType.Tcp)
            {
                props["port"] = mPort;
                props["typeFilterLevel"] = TypeFilterLevel.Full;
                mChannel = new TcpChannel(props, clientProvider, serverProvider);
            }
            else if (mChannelType == ChannelType.Http)
            {
                //props["port"] = mPort;
                //props["typeFilterLevel"] = TypeFilterLevel.Full;
                //mChannel = new HttpChannel(props, clientProvider, serverProvider);
                HttpServerChannel hc = new HttpServerChannel(mPort);
                mChannel = hc;
            }
            else if (mChannelType == ChannelType.Ipc)
            {
                //props["portName"] = mUri;
                //mChannel = new IpcChannel(props, clientProvider, serverProvider);
                IpcServerChannel sc = new IpcServerChannel(mChannelName);
                mEffectiveUri = sc.GetChannelUri();
                mChannel = sc;
            }

            #endregion

            //Register
            ChannelServices.RegisterChannel(mChannel, false);
        }
        #endregion

        #region Start
        /// <summary>
        /// Start server, allow connections
        /// </summary>
        public void Start()
        {
            Exception error = null;
            //Server current is started
            if (mIsStarted) return;

            try
            {
                #region Register Channel
                
                RegisterChannel();

                #endregion
                
                #region Start server
                //Singleton object can be used when overhead of creating an object is substantial, Every incoming message is serviced by the same object instance
                //SingleCall object can be used when overhead of creating it is insignificant, Every incoming message is serviced by a new object instance
                RemotingConfiguration.RegisterWellKnownServiceType(
                                    typeof(ServerConnector),
                                    mUri,
                                    WellKnownObjectMode.Singleton);
                #endregion

                //Server Is Started
                mIsStarted = true;

                //Event After Server Start
                RaiseStarted();
            }
            catch (Exception ex)
            {
                error = ex;

                //clean up codes
                ChannelServices.UnregisterChannel(mChannel);
            }
            //rethrow error
            if (error != null)
            {
                ServerException.Publish_CannotStartServer(error);
            }
        }

        #endregion

        #region Stop
        /// <summary>
        /// Stop server, prohibit any connections
        /// </summary>
        public void Stop()
        {
            Exception error = null;
            try
            {
                #region Unregistered All Channel
                if (mChannel != null && mIsStarted)
                {
                    IChannel[] channels = ChannelServices.RegisteredChannels;
                    foreach (IChannel channel in channels)
                    {
                        ChannelServices.UnregisterChannel(channel);
                    }
                }
                #endregion

                //Update Is Start
                mIsStarted = false;

                #region Calling the garbage collector and waiting
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                #endregion

                //Event After Server Stop
                RaiseStopped();
            }
            catch (Exception ex)
            {
                error = ex;
            }
            if (error != null)
            {
                ServerException.Publish_CannotStopServer(error);
            }
        }
        #endregion

        #region Apply Configuration
        /// <summary>
        /// Read parameters from configuration.
        /// </summary>
        void ApplyConfiguration(ConfigSection section)
        {
            //Port
            string tmp = section["Port"];
            if ((tmp != null) && (tmp != string.Empty))
            {
                #region Save Port
                Exception error = null;
                try
                {
                    mPort = int.Parse(tmp);
                }
                catch (Exception ex)
                {
                    error = ex;
                }
                #endregion
            }
            //Uri
            tmp = section["Uri"];
            if ((tmp != null) && (tmp != string.Empty)) mUri = tmp;

            //Channel Type
            tmp = section["ChannelType"];
            if ((tmp != null) && (tmp != string.Empty))
            {
                string ct = tmp.Trim().ToUpper();
                if (ct == "TCP") mChannelType = ChannelType.Tcp;
                else if (ct == "HTTP") mChannelType = ChannelType.Http;
                else if (ct == "IPC") mChannelType = ChannelType.Ipc;
            }

            tmp = section["ChannelName"];
            if ((tmp != null) && (tmp != string.Empty)) mChannelName = tmp;

            //Formatter
            tmp = section["Formatter"];
            if ((tmp != null) && (tmp != string.Empty))
            {
                string fm = tmp.Trim().ToUpper();
                if (fm == "BINARY") mFormatter = FormatterType.Binary;
                else if (fm == "SOAP") mFormatter = FormatterType.Soap;
            }

            //AutoStart
            tmp = section["AutoStart"];
            if ((tmp != null) && (tmp != string.Empty))
            {
                tmp = tmp.ToUpper();
                if (tmp == "TRUE") mAutoStart = true;
                else if (tmp == "FALSE") mAutoStart = false;
            }

            //Trace
            tmp = section["Trace"];
            if ((tmp != null) && (tmp != string.Empty))
            {
                tmp = tmp.Trim().ToUpper();
                if (tmp == "TRUE") TeleportSettings.Tracing = true;
            }
        }
        #endregion

        #region ITeleportConnector Members

        #region Teleporter
        private ITeleporter mTeleporter;
        /// <summary>
        /// Gets/Sets the teleporter this connector shall be connecting for.
        /// <para>When unassigned, the connector shall use the default teleporter.</para>
        /// </summary>
        public ITeleporter Teleporter
        {
            get
            {
                ITeleporter result = null;
                if (mTeleporter != null) result = mTeleporter;
                else result = Teleport.Teleporter;

                return result;
            }
            set { mTeleporter = value; }
        }
        #endregion

        #region Configuration
        private ConfigSection mConfiguration;
        /// <summary>
        /// Gets/Sets a custom configuration for initialization.
        /// </summary>
        public ConfigSection Configuration
        {
            get { return mConfiguration; }
            set
            {
                mConfiguration = value;
                if (mConfiguration != null) ApplyConfiguration(mConfiguration);
                else Thn.Exceptions.NullArgumentException.Publish("Configuration");
            }
        }
        #endregion

        #region Initialize
        /// <summary>
        /// Prepare this connector.
        /// </summary>
        public void Initialize()
        {            
            //Start server automatically
            if (mAutoStart) Start();
        }
        #endregion

        #endregion

        #region IServerConnector Members
        /// <summary>
        /// Teleport Request infomation from server
        /// </summary>
        public string TeleportRequest(string gateway, string method, string encodedArgs)
        {
            string result = string.Empty;
            //To ensure server is started
            if (mIsStarted)
                result= this.Teleporter.ProcessRequest(gateway, method, encodedArgs);
            return result;
        }
        #endregion
               
        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        public ServerConnector() 
        { }
        #endregion
    }
}
